/* src/interfaces/ecpg/pgtypeslib/numeric.c */

#include "postgres_fe.h"
#include <ctype.h>
#include <float.h>
#include <limits.h>

#include "extern.h"
#include "pgtypes_error.h"

#define Max(x, y)                ((x) > (y) ? (x) : (y))
#define Min(x, y)                ((x) < (y) ? (x) : (y))

#define init_var(v)                memset(v,0,sizeof(numeric))

#define digitbuf_alloc(size) ((NumericDigit *) pgtypes_alloc(size))
#define digitbuf_free(buf)        \
       do { \
                 if ((buf) != NULL) \
                          free(buf); \
          } while (0)

#include "pgtypes_numeric.h"

#if 0
/* ----------
 * apply_typmod() -
 *
 *    Do bounds checking and rounding according to the attributes
 *    typmod field.
 * ----------
 */
static int
apply_typmod(numeric *var, long typmod)
{
    int            precision;
    int            scale;
    int            maxweight;
    int            i;

    /* Do nothing if we have a default typmod (-1) */
    if (typmod < (long) (VARHDRSZ))
        return (0);

    typmod -= VARHDRSZ;
    precision = (typmod >> 16) & 0xffff;
    scale = typmod & 0xffff;
    maxweight = precision - scale;

    /* Round to target scale */
    i = scale + var->weight + 1;
    if (i >= 0 && var->ndigits > i)
    {
        int            carry = (var->digits[i] > 4) ? 1 : 0;

        var->ndigits = i;

        while (carry)
        {
            carry += var->digits[--i];
            var->digits[i] = carry % 10;
            carry /= 10;
        }

        if (i < 0)
        {
            var->digits--;
            var->ndigits++;
            var->weight++;
        }
    }
    else
        var->ndigits = Max(0, Min(i, var->ndigits));

    /*
     * Check for overflow - note we can't do this before rounding, because
     * rounding could raise the weight.  Also note that the var's weight could
     * be inflated by leading zeroes, which will be stripped before storage
     * but perhaps might not have been yet. In any case, we must recognize a
     * true zero, whose weight doesn't mean anything.
     */
    if (var->weight >= maxweight)
    {
        /* Determine true weight; and check for all-zero result */
        int            tweight = var->weight;

        for (i = 0; i < var->ndigits; i++)
        {
            if (var->digits[i])
                break;
            tweight--;
        }

        if (tweight >= maxweight && i < var->ndigits)
        {
            errno = PGTYPES_NUM_OVERFLOW;
            return -1;
        }
    }

    var->rscale = scale;
    var->dscale = scale;
    return (0);
}
#endif

/* ----------
 *    alloc_var() -
 *
 *     Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
 * ----------
 */
static int
alloc_var(numeric *var, int ndigits)
{
    digitbuf_free(var->buf);
    var->buf = digitbuf_alloc(ndigits + 1);
    if (var->buf == NULL)
        return -1;
    var->buf[0] = 0;
    var->digits = var->buf + 1;
    var->ndigits = ndigits;
    return 0;
}

numeric *
PGTYPESnumeric_new(void)
{
    numeric    *var;

    if ((var = (numeric *) pgtypes_alloc(sizeof(numeric))) == NULL)
        return NULL;

    if (alloc_var(var, 0) < 0)
    {
        free(var);
        return NULL;
    }

    return var;
}

decimal *
PGTYPESdecimal_new(void)
{
    decimal    *var;

    if ((var = (decimal *) pgtypes_alloc(sizeof(decimal))) == NULL)
        return NULL;

    memset(var, 0, sizeof(decimal));

    return var;
}

/* ----------
 * set_var_from_str()
 *
 *    Parse a string and put the number into a variable
 * ----------
 */
static int
set_var_from_str(char *str, char **ptr, numeric *dest)
{
    bool        have_dp = FALSE;
    int            i = 0;

    errno = 0;
    *ptr = str;
    while (*(*ptr))
    {
        if (!isspace((unsigned char) *(*ptr)))
            break;
        (*ptr)++;
    }

    if (pg_strncasecmp(*ptr, "NaN", 3) == 0)
    {
        *ptr += 3;
        dest->sign = NUMERIC_NAN;

        /* Should be nothing left but spaces */
        while (*(*ptr))
        {
            if (!isspace((unsigned char) *(*ptr)))
            {
                errno = PGTYPES_NUM_BAD_NUMERIC;
                return -1;
            }
            (*ptr)++;
        }

        return 0;
    }

    if (alloc_var(dest, strlen((*ptr))) < 0)
        return -1;
    dest->weight = -1;
    dest->dscale = 0;
    dest->sign = NUMERIC_POS;

    switch (*(*ptr))
    {
        case '+':
            dest->sign = NUMERIC_POS;
            (*ptr)++;
            break;

        case '-':
            dest->sign = NUMERIC_NEG;
            (*ptr)++;
            break;
    }

    if (*(*ptr) == '.')
    {
        have_dp = TRUE;
        (*ptr)++;
    }

    if (!isdigit((unsigned char) *(*ptr)))
    {
        errno = PGTYPES_NUM_BAD_NUMERIC;
        return -1;
    }

    while (*(*ptr))
    {
        if (isdigit((unsigned char) *(*ptr)))
        {
            dest->digits[i++] = *(*ptr)++ - '0';
            if (!have_dp)
                dest->weight++;
            else
                dest->dscale++;
        }
        else if (*(*ptr) == '.')
        {
            if (have_dp)
            {
                errno = PGTYPES_NUM_BAD_NUMERIC;
                return -1;
            }
            have_dp = TRUE;
            (*ptr)++;
        }
        else
            break;
    }
    dest->ndigits = i;

    /* Handle exponent, if any */
    if (*(*ptr) == 'e' || *(*ptr) == 'E')
    {
        long        exponent;
        char       *endptr;

        (*ptr)++;
        exponent = strtol(*ptr, &endptr, 10);
        if (endptr == (*ptr))
        {
            errno = PGTYPES_NUM_BAD_NUMERIC;
            return -1;
        }
        (*ptr) = endptr;
        if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
        {
            errno = PGTYPES_NUM_BAD_NUMERIC;
            return -1;
        }
        dest->weight += (int) exponent;
        dest->dscale -= (int) exponent;
        if (dest->dscale < 0)
            dest->dscale = 0;
    }

    /* Should be nothing left but spaces */
    while (*(*ptr))
    {
        if (!isspace((unsigned char) *(*ptr)))
        {
            errno = PGTYPES_NUM_BAD_NUMERIC;
            return -1;
        }
        (*ptr)++;
    }

    /* Strip any leading zeroes */
    while (dest->ndigits > 0 && *(dest->digits) == 0)
    {
        (dest->digits)++;
        (dest->weight)--;
        (dest->ndigits)--;
    }
    if (dest->ndigits == 0)
        dest->weight = 0;

    dest->rscale = dest->dscale;
    return (0);
}


/* ----------
 * get_str_from_var() -
 *
 *    Convert a var to text representation (guts of numeric_out).
 *    CAUTION: var's contents may be modified by rounding!
 * ----------
 */
static char *
get_str_from_var(numeric *var, int dscale)
{
    char       *str;
    char       *cp;
    int            i;
    int            d;

    if (var->sign == NUMERIC_NAN)
    {
        str = (char *) pgtypes_alloc(4);
        if (str == NULL)
            return NULL;
        sprintf(str, "NaN");
        return str;
    }

    /*
     * Check if we must round up before printing the value and do so.
     */
    i = dscale + var->weight + 1;
    if (i >= 0 && var->ndigits > i)
    {
        int            carry = (var->digits[i] > 4) ? 1 : 0;

        var->ndigits = i;

        while (carry)
        {
            carry += var->digits[--i];
            var->digits[i] = carry % 10;
            carry /= 10;
        }

        if (i < 0)
        {
            var->digits--;
            var->ndigits++;
            var->weight++;
        }
    }
    else
        var->ndigits = Max(0, Min(i, var->ndigits));

    /*
     * Allocate space for the result
     */
    if ((str = (char *) pgtypes_alloc(Max(0, dscale) + Max(0, var->weight) + 4)) == NULL)
        return NULL;
    cp = str;

    /*
     * Output a dash for negative values
     */
    if (var->sign == NUMERIC_NEG)
        *cp++ = '-';

    /*
     * Output all digits before the decimal point
     */
    i = Max(var->weight, 0);
    d = 0;

    while (i >= 0)
    {
        if (i <= var->weight && d < var->ndigits)
            *cp++ = var->digits[d++] + '0';
        else
            *cp++ = '0';
        i--;
    }

    /*
     * If requested, output a decimal point and all the digits that follow it.
     */
    if (dscale > 0)
    {
        *cp++ = '.';
        while (i >= -dscale)
        {
            if (i <= var->weight && d < var->ndigits)
                *cp++ = var->digits[d++] + '0';
            else
                *cp++ = '0';
            i--;
        }
    }

    /*
     * terminate the string and return it
     */
    *cp = '\0';
    return str;
}

numeric *
PGTYPESnumeric_from_asc(char *str, char **endptr)
{
    numeric    *value = (numeric *) pgtypes_alloc(sizeof(numeric));
    int            ret;

    char       *realptr;
    char      **ptr = (endptr != NULL) ? endptr : &realptr;

    if (!value)
        return (NULL);

    ret = set_var_from_str(str, ptr, value);
    if (ret)
    {
        PGTYPESnumeric_free(value);
        return (NULL);
    }

    return (value);
}

char *
PGTYPESnumeric_to_asc(numeric *num, int dscale)
{
    numeric    *numcopy = PGTYPESnumeric_new();
    char       *s;

    if (numcopy == NULL)
        return NULL;

    if (PGTYPESnumeric_copy(num, numcopy) < 0)
    {
        PGTYPESnumeric_free(numcopy);
        return NULL;
    }

    if (dscale < 0)
        dscale = num->dscale;

    /* get_str_from_var may change its argument */
    s = get_str_from_var(numcopy, dscale);
    PGTYPESnumeric_free(numcopy);
    return (s);
}

/* ----------
 * zero_var() -
 *
 *    Set a variable to ZERO.
 *    Note: rscale and dscale are not touched.
 * ----------
 */
static void
zero_var(numeric *var)
{
    digitbuf_free(var->buf);
    var->buf = NULL;
    var->digits = NULL;
    var->ndigits = 0;
    var->weight = 0;            /* by convention; doesn't really matter */
    var->sign = NUMERIC_POS;    /* anything but NAN... */
}

void
PGTYPESnumeric_free(numeric *var)
{
    digitbuf_free(var->buf);
    free(var);
}

void
PGTYPESdecimal_free(decimal *var)
{
    free(var);
}

/* ----------
 * cmp_abs() -
 *
 *    Compare the absolute values of var1 and var2
 *    Returns:    -1 for ABS(var1) < ABS(var2)
 *                0  for ABS(var1) == ABS(var2)
 *                1  for ABS(var1) > ABS(var2)
 * ----------
 */
static int
cmp_abs(numeric *var1, numeric *var2)
{
    int            i1 = 0;
    int            i2 = 0;
    int            w1 = var1->weight;
    int            w2 = var2->weight;
    int            stat;

    while (w1 > w2 && i1 < var1->ndigits)
    {
        if (var1->digits[i1++] != 0)
            return 1;
        w1--;
    }
    while (w2 > w1 && i2 < var2->ndigits)
    {
        if (var2->digits[i2++] != 0)
            return -1;
        w2--;
    }

    if (w1 == w2)
    {
        while (i1 < var1->ndigits && i2 < var2->ndigits)
        {
            stat = var1->digits[i1++] - var2->digits[i2++];
            if (stat)
            {
                if (stat > 0)
                    return 1;
                return -1;
            }
        }
    }

    while (i1 < var1->ndigits)
    {
        if (var1->digits[i1++] != 0)
            return 1;
    }
    while (i2 < var2->ndigits)
    {
        if (var2->digits[i2++] != 0)
            return -1;
    }

    return 0;
}


/* ----------
 * add_abs() -
 *
 *    Add the absolute values of two variables into result.
 *    result might point to one of the operands without danger.
 * ----------
 */
static int
add_abs(numeric *var1, numeric *var2, numeric *result)
{
    NumericDigit *res_buf;
    NumericDigit *res_digits;
    int            res_ndigits;
    int            res_weight;
    int            res_rscale;
    int            res_dscale;
    int            i,
                i1,
                i2;
    int            carry = 0;

    /* copy these values into local vars for speed in inner loop */
    int            var1ndigits = var1->ndigits;
    int            var2ndigits = var2->ndigits;
    NumericDigit *var1digits = var1->digits;
    NumericDigit *var2digits = var2->digits;

    res_weight = Max(var1->weight, var2->weight) + 1;
    res_rscale = Max(var1->rscale, var2->rscale);
    res_dscale = Max(var1->dscale, var2->dscale);
    res_ndigits = res_rscale + res_weight + 1;
    if (res_ndigits <= 0)
        res_ndigits = 1;

    if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
        return -1;
    res_digits = res_buf;

    i1 = res_rscale + var1->weight + 1;
    i2 = res_rscale + var2->weight + 1;
    for (i = res_ndigits - 1; i >= 0; i--)
    {
        i1--;
        i2--;
        if (i1 >= 0 && i1 < var1ndigits)
            carry += var1digits[i1];
        if (i2 >= 0 && i2 < var2ndigits)
            carry += var2digits[i2];

        if (carry >= 10)
        {
            res_digits[i] = carry - 10;
            carry = 1;
        }
        else
        {
            res_digits[i] = carry;
            carry = 0;
        }
    }

    while (res_ndigits > 0 && *res_digits == 0)
    {
        res_digits++;
        res_weight--;
        res_ndigits--;
    }
    while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
        res_ndigits--;

    if (res_ndigits == 0)
        res_weight = 0;

    digitbuf_free(result->buf);
    result->ndigits = res_ndigits;
    result->buf = res_buf;
    result->digits = res_digits;
    result->weight = res_weight;
    result->rscale = res_rscale;
    result->dscale = res_dscale;

    return 0;
}


/* ----------
 * sub_abs() -
 *
 *    Subtract the absolute value of var2 from the absolute value of var1
 *    and store in result. result might point to one of the operands
 *    without danger.
 *
 *    ABS(var1) MUST BE GREATER OR EQUAL ABS(var2) !!!
 * ----------
 */
static int
sub_abs(numeric *var1, numeric *var2, numeric *result)
{
    NumericDigit *res_buf;
    NumericDigit *res_digits;
    int            res_ndigits;
    int            res_weight;
    int            res_rscale;
    int            res_dscale;
    int            i,
                i1,
                i2;
    int            borrow = 0;

    /* copy these values into local vars for speed in inner loop */
    int            var1ndigits = var1->ndigits;
    int            var2ndigits = var2->ndigits;
    NumericDigit *var1digits = var1->digits;
    NumericDigit *var2digits = var2->digits;

    res_weight = var1->weight;
    res_rscale = Max(var1->rscale, var2->rscale);
    res_dscale = Max(var1->dscale, var2->dscale);
    res_ndigits = res_rscale + res_weight + 1;
    if (res_ndigits <= 0)
        res_ndigits = 1;

    if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
        return -1;
    res_digits = res_buf;

    i1 = res_rscale + var1->weight + 1;
    i2 = res_rscale + var2->weight + 1;
    for (i = res_ndigits - 1; i >= 0; i--)
    {
        i1--;
        i2--;
        if (i1 >= 0 && i1 < var1ndigits)
            borrow += var1digits[i1];
        if (i2 >= 0 && i2 < var2ndigits)
            borrow -= var2digits[i2];

        if (borrow < 0)
        {
            res_digits[i] = borrow + 10;
            borrow = -1;
        }
        else
        {
            res_digits[i] = borrow;
            borrow = 0;
        }
    }

    while (res_ndigits > 0 && *res_digits == 0)
    {
        res_digits++;
        res_weight--;
        res_ndigits--;
    }
    while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
        res_ndigits--;

    if (res_ndigits == 0)
        res_weight = 0;

    digitbuf_free(result->buf);
    result->ndigits = res_ndigits;
    result->buf = res_buf;
    result->digits = res_digits;
    result->weight = res_weight;
    result->rscale = res_rscale;
    result->dscale = res_dscale;

    return 0;
}

/* ----------
 * add_var() -
 *
 *    Full version of add functionality on variable level (handling signs).
 *    result might point to one of the operands too without danger.
 * ----------
 */
int
PGTYPESnumeric_add(numeric *var1, numeric *var2, numeric *result)
{
    /*
     * Decide on the signs of the two variables what to do
     */
    if (var1->sign == NUMERIC_POS)
    {
        if (var2->sign == NUMERIC_POS)
        {
            /*
             * Both are positive result = +(ABS(var1) + ABS(var2))
             */
            if (add_abs(var1, var2, result) != 0)
                return -1;
            result->sign = NUMERIC_POS;
        }
        else
        {
            /*
             * var1 is positive, var2 is negative Must compare absolute values
             */
            switch (cmp_abs(var1, var2))
            {
                case 0:
                    /* ----------
                     * ABS(var1) == ABS(var2)
                     * result = ZERO
                     * ----------
                     */
                    zero_var(result);
                    result->rscale = Max(var1->rscale, var2->rscale);
                    result->dscale = Max(var1->dscale, var2->dscale);
                    break;

                case 1:
                    /* ----------
                     * ABS(var1) > ABS(var2)
                     * result = +(ABS(var1) - ABS(var2))
                     * ----------
                     */
                    if (sub_abs(var1, var2, result) != 0)
                        return -1;
                    result->sign = NUMERIC_POS;
                    break;

                case -1:
                    /* ----------
                     * ABS(var1) < ABS(var2)
                     * result = -(ABS(var2) - ABS(var1))
                     * ----------
                     */
                    if (sub_abs(var2, var1, result) != 0)
                        return -1;
                    result->sign = NUMERIC_NEG;
                    break;
            }
        }
    }
    else
    {
        if (var2->sign == NUMERIC_POS)
        {
            /* ----------
             * var1 is negative, var2 is positive
             * Must compare absolute values
             * ----------
             */
            switch (cmp_abs(var1, var2))
            {
                case 0:
                    /* ----------
                     * ABS(var1) == ABS(var2)
                     * result = ZERO
                     * ----------
                     */
                    zero_var(result);
                    result->rscale = Max(var1->rscale, var2->rscale);
                    result->dscale = Max(var1->dscale, var2->dscale);
                    break;

                case 1:
                    /* ----------
                     * ABS(var1) > ABS(var2)
                     * result = -(ABS(var1) - ABS(var2))
                     * ----------
                     */
                    if (sub_abs(var1, var2, result) != 0)
                        return -1;
                    result->sign = NUMERIC_NEG;
                    break;

                case -1:
                    /* ----------
                     * ABS(var1) < ABS(var2)
                     * result = +(ABS(var2) - ABS(var1))
                     * ----------
                     */
                    if (sub_abs(var2, var1, result) != 0)
                        return -1;
                    result->sign = NUMERIC_POS;
                    break;
            }
        }
        else
        {
            /* ----------
             * Both are negative
             * result = -(ABS(var1) + ABS(var2))
             * ----------
             */
            if (add_abs(var1, var2, result) != 0)
                return -1;
            result->sign = NUMERIC_NEG;
        }
    }

    return 0;
}


/* ----------
 * sub_var() -
 *
 *    Full version of sub functionality on variable level (handling signs).
 *    result might point to one of the operands too without danger.
 * ----------
 */
int
PGTYPESnumeric_sub(numeric *var1, numeric *var2, numeric *result)
{
    /*
     * Decide on the signs of the two variables what to do
     */
    if (var1->sign == NUMERIC_POS)
    {
        if (var2->sign == NUMERIC_NEG)
        {
            /* ----------
             * var1 is positive, var2 is negative
             * result = +(ABS(var1) + ABS(var2))
             * ----------
             */
            if (add_abs(var1, var2, result) != 0)
                return -1;
            result->sign = NUMERIC_POS;
        }
        else
        {
            /* ----------
             * Both are positive
             * Must compare absolute values
             * ----------
             */
            switch (cmp_abs(var1, var2))
            {
                case 0:
                    /* ----------
                     * ABS(var1) == ABS(var2)
                     * result = ZERO
                     * ----------
                     */
                    zero_var(result);
                    result->rscale = Max(var1->rscale, var2->rscale);
                    result->dscale = Max(var1->dscale, var2->dscale);
                    break;

                case 1:
                    /* ----------
                     * ABS(var1) > ABS(var2)
                     * result = +(ABS(var1) - ABS(var2))
                     * ----------
                     */
                    if (sub_abs(var1, var2, result) != 0)
                        return -1;
                    result->sign = NUMERIC_POS;
                    break;

                case -1:
                    /* ----------
                     * ABS(var1) < ABS(var2)
                     * result = -(ABS(var2) - ABS(var1))
                     * ----------
                     */
                    if (sub_abs(var2, var1, result) != 0)
                        return -1;
                    result->sign = NUMERIC_NEG;
                    break;
            }
        }
    }
    else
    {
        if (var2->sign == NUMERIC_NEG)
        {
            /* ----------
             * Both are negative
             * Must compare absolute values
             * ----------
             */
            switch (cmp_abs(var1, var2))
            {
                case 0:
                    /* ----------
                     * ABS(var1) == ABS(var2)
                     * result = ZERO
                     * ----------
                     */
                    zero_var(result);
                    result->rscale = Max(var1->rscale, var2->rscale);
                    result->dscale = Max(var1->dscale, var2->dscale);
                    break;

                case 1:
                    /* ----------
                     * ABS(var1) > ABS(var2)
                     * result = -(ABS(var1) - ABS(var2))
                     * ----------
                     */
                    if (sub_abs(var1, var2, result) != 0)
                        return -1;
                    result->sign = NUMERIC_NEG;
                    break;

                case -1:
                    /* ----------
                     * ABS(var1) < ABS(var2)
                     * result = +(ABS(var2) - ABS(var1))
                     * ----------
                     */
                    if (sub_abs(var2, var1, result) != 0)
                        return -1;
                    result->sign = NUMERIC_POS;
                    break;
            }
        }
        else
        {
            /* ----------
             * var1 is negative, var2 is positive
             * result = -(ABS(var1) + ABS(var2))
             * ----------
             */
            if (add_abs(var1, var2, result) != 0)
                return -1;
            result->sign = NUMERIC_NEG;
        }
    }

    return 0;
}

/* ----------
 * mul_var() -
 *
 *    Multiplication on variable level. Product of var1 * var2 is stored
 *    in result.  Accuracy of result is determined by global_rscale.
 * ----------
 */
int
PGTYPESnumeric_mul(numeric *var1, numeric *var2, numeric *result)
{
    NumericDigit *res_buf;
    NumericDigit *res_digits;
    int            res_ndigits;
    int            res_weight;
    int            res_sign;
    int            i,
                ri,
                i1,
                i2;
    long        sum = 0;
    int            global_rscale = var1->rscale + var2->rscale;

    res_weight = var1->weight + var2->weight + 2;
    res_ndigits = var1->ndigits + var2->ndigits + 1;
    if (var1->sign == var2->sign)
        res_sign = NUMERIC_POS;
    else
        res_sign = NUMERIC_NEG;

    if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
        return -1;
    res_digits = res_buf;
    memset(res_digits, 0, res_ndigits);

    ri = res_ndigits;
    for (i1 = var1->ndigits - 1; i1 >= 0; i1--)
    {
        sum = 0;
        i = --ri;

        for (i2 = var2->ndigits - 1; i2 >= 0; i2--)
        {
            sum += res_digits[i] + var1->digits[i1] * var2->digits[i2];
            res_digits[i--] = sum % 10;
            sum /= 10;
        }
        res_digits[i] = sum;
    }

    i = res_weight + global_rscale + 2;
    if (i >= 0 && i < res_ndigits)
    {
        sum = (res_digits[i] > 4) ? 1 : 0;
        res_ndigits = i;
        i--;
        while (sum)
        {
            sum += res_digits[i];
            res_digits[i--] = sum % 10;
            sum /= 10;
        }
    }

    while (res_ndigits > 0 && *res_digits == 0)
    {
        res_digits++;
        res_weight--;
        res_ndigits--;
    }
    while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
        res_ndigits--;

    if (res_ndigits == 0)
    {
        res_sign = NUMERIC_POS;
        res_weight = 0;
    }

    digitbuf_free(result->buf);
    result->buf = res_buf;
    result->digits = res_digits;
    result->ndigits = res_ndigits;
    result->weight = res_weight;
    result->rscale = global_rscale;
    result->sign = res_sign;
    result->dscale = var1->dscale + var2->dscale;

    return 0;
}

/*
 * Default scale selection for division
 *
 * Returns the appropriate display scale for the division result,
 * and sets global_rscale to the result scale to use during div_var.
 *
 * Note that this must be called before div_var.
 */
static int
select_div_scale(numeric *var1, numeric *var2, int *rscale)
{
    int            weight1,
                weight2,
                qweight,
                i;
    NumericDigit firstdigit1,
                firstdigit2;
    int            res_dscale;

    /*
     * The result scale of a division isn't specified in any SQL standard. For
     * PostgreSQL we select a display scale that will give at least
     * NUMERIC_MIN_SIG_DIGITS significant digits, so that numeric gives a
     * result no less accurate than float8; but use a scale not less than
     * either input's display scale.
     */

    /* Get the actual (normalized) weight and first digit of each input */

    weight1 = 0;                /* values to use if var1 is zero */
    firstdigit1 = 0;
    for (i = 0; i < var1->ndigits; i++)
    {
        firstdigit1 = var1->digits[i];
        if (firstdigit1 != 0)
        {
            weight1 = var1->weight - i;
            break;
        }
    }

    weight2 = 0;                /* values to use if var2 is zero */
    firstdigit2 = 0;
    for (i = 0; i < var2->ndigits; i++)
    {
        firstdigit2 = var2->digits[i];
        if (firstdigit2 != 0)
        {
            weight2 = var2->weight - i;
            break;
        }
    }

    /*
     * Estimate weight of quotient.  If the two first digits are equal, we
     * can't be sure, but assume that var1 is less than var2.
     */
    qweight = weight1 - weight2;
    if (firstdigit1 <= firstdigit2)
        qweight--;

    /* Select display scale */
    res_dscale = NUMERIC_MIN_SIG_DIGITS - qweight;
    res_dscale = Max(res_dscale, var1->dscale);
    res_dscale = Max(res_dscale, var2->dscale);
    res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
    res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);

    /* Select result scale */
    *rscale = res_dscale + 4;

    return res_dscale;
}

int
PGTYPESnumeric_div(numeric *var1, numeric *var2, numeric *result)
{
    NumericDigit *res_digits;
    int            res_ndigits;
    int            res_sign;
    int            res_weight;
    numeric        dividend;
    numeric        divisor[10];
    int            ndigits_tmp;
    int            weight_tmp;
    int            rscale_tmp;
    int            ri;
    int            i;
    long        guess;
    long        first_have;
    long        first_div;
    int            first_nextdigit;
    int            stat = 0;
    int            rscale;
    int            res_dscale = select_div_scale(var1, var2, &rscale);
    int            err = -1;
    NumericDigit *tmp_buf;

    /*
     * First of all division by zero check
     */
    ndigits_tmp = var2->ndigits + 1;
    if (ndigits_tmp == 1)
    {
        errno = PGTYPES_NUM_DIVIDE_ZERO;
        return -1;
    }

    /*
     * Determine the result sign, weight and number of digits to calculate
     */
    if (var1->sign == var2->sign)
        res_sign = NUMERIC_POS;
    else
        res_sign = NUMERIC_NEG;
    res_weight = var1->weight - var2->weight + 1;
    res_ndigits = rscale + res_weight;
    if (res_ndigits <= 0)
        res_ndigits = 1;

    /*
     * Now result zero check
     */
    if (var1->ndigits == 0)
    {
        zero_var(result);
        result->rscale = rscale;
        return 0;
    }

    /*
     * Initialize local variables
     */
    init_var(&dividend);
    for (i = 1; i < 10; i++)
        init_var(&divisor[i]);

    /*
     * Make a copy of the divisor which has one leading zero digit
     */
    divisor[1].ndigits = ndigits_tmp;
    divisor[1].rscale = var2->ndigits;
    divisor[1].sign = NUMERIC_POS;
    divisor[1].buf = digitbuf_alloc(ndigits_tmp);
    if (divisor[1].buf == NULL)
        goto done;
    divisor[1].digits = divisor[1].buf;
    divisor[1].digits[0] = 0;
    memcpy(&(divisor[1].digits[1]), var2->digits, ndigits_tmp - 1);

    /*
     * Make a copy of the dividend
     */
    dividend.ndigits = var1->ndigits;
    dividend.weight = 0;
    dividend.rscale = var1->ndigits;
    dividend.sign = NUMERIC_POS;
    dividend.buf = digitbuf_alloc(var1->ndigits);
    if (dividend.buf == NULL)
        goto done;
    dividend.digits = dividend.buf;
    memcpy(dividend.digits, var1->digits, var1->ndigits);

    /*
     * Setup the result. Do the allocation in a temporary buffer first, so we
     * don't free result->buf unless we have successfully allocated a buffer
     * to replace it with.
     */
    tmp_buf = digitbuf_alloc(res_ndigits + 2);
    if (tmp_buf == NULL)
        goto done;
    digitbuf_free(result->buf);
    result->buf = tmp_buf;
    res_digits = result->buf;
    result->digits = res_digits;
    result->ndigits = res_ndigits;
    result->weight = res_weight;
    result->rscale = rscale;
    result->sign = res_sign;
    res_digits[0] = 0;

    first_div = divisor[1].digits[1] * 10;
    if (ndigits_tmp > 2)
        first_div += divisor[1].digits[2];

    first_have = 0;
    first_nextdigit = 0;

    weight_tmp = 1;
    rscale_tmp = divisor[1].rscale;

    for (ri = 0; ri <= res_ndigits; ri++)
    {
        first_have = first_have * 10;
        if (first_nextdigit >= 0 && first_nextdigit < dividend.ndigits)
            first_have += dividend.digits[first_nextdigit];
        first_nextdigit++;

        guess = (first_have * 10) / first_div + 1;
        if (guess > 9)
            guess = 9;

        while (guess > 0)
        {
            if (divisor[guess].buf == NULL)
            {
                int            i;
                long        sum = 0;

                memcpy(&divisor[guess], &divisor[1], sizeof(numeric));
                divisor[guess].buf = digitbuf_alloc(divisor[guess].ndigits);
                if (divisor[guess].buf == NULL)
                    goto done;
                divisor[guess].digits = divisor[guess].buf;
                for (i = divisor[1].ndigits - 1; i >= 0; i--)
                {
                    sum += divisor[1].digits[i] * guess;
                    divisor[guess].digits[i] = sum % 10;
                    sum /= 10;
                }
            }

            divisor[guess].weight = weight_tmp;
            divisor[guess].rscale = rscale_tmp;

            stat = cmp_abs(&dividend, &divisor[guess]);
            if (stat >= 0)
                break;

            guess--;
        }

        res_digits[ri + 1] = guess;
        if (stat == 0)
        {
            ri++;
            break;
        }

        weight_tmp--;
        rscale_tmp++;

        if (guess == 0)
            continue;

        if (sub_abs(&dividend, &divisor[guess], &dividend) != 0)
            goto done;

        first_nextdigit = dividend.weight - weight_tmp;
        first_have = 0;
        if (first_nextdigit >= 0 && first_nextdigit < dividend.ndigits)
            first_have = dividend.digits[first_nextdigit];
        first_nextdigit++;
    }

    result->ndigits = ri + 1;
    if (ri == res_ndigits + 1)
    {
        int            carry = (res_digits[ri] > 4) ? 1 : 0;

        result->ndigits = ri;
        res_digits[ri] = 0;

        while (carry && ri > 0)
        {
            carry += res_digits[--ri];
            res_digits[ri] = carry % 10;
            carry /= 10;
        }
    }

    while (result->ndigits > 0 && *(result->digits) == 0)
    {
        (result->digits)++;
        (result->weight)--;
        (result->ndigits)--;
    }
    while (result->ndigits > 0 && result->digits[result->ndigits - 1] == 0)
        (result->ndigits)--;
    if (result->ndigits == 0)
        result->sign = NUMERIC_POS;

    result->dscale = res_dscale;
    err = 0;                    /* if we've made it this far, return success */

done:

    /*
     * Tidy up
     */
    if (dividend.buf != NULL)
        digitbuf_free(dividend.buf);

    for (i = 1; i < 10; i++)
    {
        if (divisor[i].buf != NULL)
            digitbuf_free(divisor[i].buf);
    }

    return err;
}


int
PGTYPESnumeric_cmp(numeric *var1, numeric *var2)
{
    /* use cmp_abs function to calculate the result */

    /* both are positive: normal comparison with cmp_abs */
    if (var1->sign == NUMERIC_POS && var2->sign == NUMERIC_POS)
        return cmp_abs(var1, var2);

    /* both are negative: return the inverse of the normal comparison */
    if (var1->sign == NUMERIC_NEG && var2->sign == NUMERIC_NEG)
    {
        /*
         * instead of inverting the result, we invert the parameter ordering
         */
        return cmp_abs(var2, var1);
    }

    /* one is positive, one is negative: trivial */
    if (var1->sign == NUMERIC_POS && var2->sign == NUMERIC_NEG)
        return 1;
    if (var1->sign == NUMERIC_NEG && var2->sign == NUMERIC_POS)
        return -1;

    errno = PGTYPES_NUM_BAD_NUMERIC;
    return INT_MAX;

}

int
PGTYPESnumeric_from_int(signed int int_val, numeric *var)
{
    /* implicit conversion */
    signed long int long_int = int_val;

    return PGTYPESnumeric_from_long(long_int, var);
}

int
PGTYPESnumeric_from_long(signed long int long_val, numeric *var)
{
    /* calculate the size of the long int number */
    /* a number n needs log_10 n digits */

    /*
     * however we multiply by 10 each time and compare instead of calculating
     * the logarithm
     */

    int            size = 0;
    int            i;
    signed long int abs_long_val = long_val;
    signed long int extract;
    signed long int reach_limit;

    if (abs_long_val < 0)
    {
        abs_long_val *= -1;
        var->sign = NUMERIC_NEG;
    }
    else
        var->sign = NUMERIC_POS;

    reach_limit = 1;
    do
    {
        size++;
        reach_limit *= 10;
    } while (reach_limit - 1 < abs_long_val && reach_limit <= LONG_MAX / 10);

    if (reach_limit > LONG_MAX / 10)
    {
        /* add the first digit and a .0 */
        size += 2;
    }
    else
    {
        /* always add a .0 */
        size++;
        reach_limit /= 10;
    }

    if (alloc_var(var, size) < 0)
        return -1;

    var->rscale = 1;
    var->dscale = 1;
    var->weight = size - 2;

    i = 0;
    do
    {
        extract = abs_long_val - (abs_long_val % reach_limit);
        var->digits[i] = extract / reach_limit;
        abs_long_val -= extract;
        i++;
        reach_limit /= 10;

        /*
         * we can abandon if abs_long_val reaches 0, because the memory is
         * initialized properly and filled with '0', so converting 10000 in
         * only one step is no problem
         */
    } while (abs_long_val > 0);

    return 0;
}

int
PGTYPESnumeric_copy(numeric *src, numeric *dst)
{
    int            i;

    if (dst == NULL)
        return -1;
    zero_var(dst);

    dst->weight = src->weight;
    dst->rscale = src->rscale;
    dst->dscale = src->dscale;
    dst->sign = src->sign;

    if (alloc_var(dst, src->ndigits) != 0)
        return -1;

    for (i = 0; i < src->ndigits; i++)
        dst->digits[i] = src->digits[i];

    return 0;
}

int
PGTYPESnumeric_from_double(double d, numeric *dst)
{
    char        buffer[DBL_DIG + 100];
    numeric    *tmp;
    int            i;

    if (sprintf(buffer, "%.*g", DBL_DIG, d) <= 0)
        return -1;

    if ((tmp = PGTYPESnumeric_from_asc(buffer, NULL)) == NULL)
        return -1;
    i = PGTYPESnumeric_copy(tmp, dst);
    PGTYPESnumeric_free(tmp);
    if (i != 0)
        return -1;

    errno = 0;
    return 0;
}

static int
numericvar_to_double(numeric *var, double *dp)
{
    char       *tmp;
    double        val;
    char       *endptr;
    numeric    *varcopy = PGTYPESnumeric_new();

    if (varcopy == NULL)
        return -1;

    if (PGTYPESnumeric_copy(var, varcopy) < 0)
    {
        PGTYPESnumeric_free(varcopy);
        return -1;
    }

    tmp = get_str_from_var(varcopy, varcopy->dscale);
    PGTYPESnumeric_free(varcopy);

    if (tmp == NULL)
        return -1;

    /*
     * strtod does not reset errno to 0 in case of success.
     */
    errno = 0;
    val = strtod(tmp, &endptr);
    if (errno == ERANGE)
    {
        free(tmp);
        if (val == 0)
            errno = PGTYPES_NUM_UNDERFLOW;
        else
            errno = PGTYPES_NUM_OVERFLOW;
        return -1;
    }

    /* can't free tmp yet, endptr points still into it */
    if (*endptr != '\0')
    {
        /* shouldn't happen ... */
        free(tmp);
        errno = PGTYPES_NUM_BAD_NUMERIC;
        return -1;
    }
    free(tmp);
    *dp = val;
    return 0;
}

int
PGTYPESnumeric_to_double(numeric *nv, double *dp)
{
    double        tmp;

    if (numericvar_to_double(nv, &tmp) != 0)
        return -1;
    *dp = tmp;
    return 0;
}

int
PGTYPESnumeric_to_int(numeric *nv, int *ip)
{
    long        l;
    int            i;

    if ((i = PGTYPESnumeric_to_long(nv, &l)) != 0)
        return i;

    if (l < -INT_MAX || l > INT_MAX)
    {
        errno = PGTYPES_NUM_OVERFLOW;
        return -1;
    }

    *ip = (int) l;
    return 0;
}

int
PGTYPESnumeric_to_long(numeric *nv, long *lp)
{
    char       *s = PGTYPESnumeric_to_asc(nv, 0);
    char       *endptr;

    if (s == NULL)
        return -1;

    errno = 0;
    *lp = strtol(s, &endptr, 10);
    if (endptr == s)
    {
        /* this should not happen actually */
        free(s);
        return -1;
    }
    free(s);
    if (errno == ERANGE)
    {
        if (*lp == LONG_MIN)
            errno = PGTYPES_NUM_UNDERFLOW;
        else
            errno = PGTYPES_NUM_OVERFLOW;
        return -1;
    }
    return 0;
}

int
PGTYPESnumeric_to_decimal(numeric *src, decimal *dst)
{
    int            i;

    if (src->ndigits > DECSIZE)
    {
        errno = PGTYPES_NUM_OVERFLOW;
        return -1;
    }

    dst->weight = src->weight;
    dst->rscale = src->rscale;
    dst->dscale = src->dscale;
    dst->sign = src->sign;
    dst->ndigits = src->ndigits;

    for (i = 0; i < src->ndigits; i++)
        dst->digits[i] = src->digits[i];

    return 0;
}

int
PGTYPESnumeric_from_decimal(decimal *src, numeric *dst)
{
    int            i;

    zero_var(dst);

    dst->weight = src->weight;
    dst->rscale = src->rscale;
    dst->dscale = src->dscale;
    dst->sign = src->sign;

    if (alloc_var(dst, src->ndigits) != 0)
        return -1;

    for (i = 0; i < src->ndigits; i++)
        dst->digits[i] = src->digits[i];

    return 0;
}
